<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="J0ZYp" id="J0ZYp"><span data-lake-id="ue5728cbe" id="ue5728cbe">典型回答</span></h1>
  <p data-lake-id="u3aea8e9b" id="u3aea8e9b"><br></p>
  <p data-lake-id="u0c58ef2f" id="u0c58ef2f"><span data-lake-id="ucd659157" id="ucd659157">RabbitMQ中是可以实现延迟消息的，一般有两种方式，分别是通过死信队列以及通过延迟消息插件来实现。</span></p>
  <p data-lake-id="u4d1ec2f1" id="u4d1ec2f1"><span data-lake-id="u22a18871" id="u22a18871">​</span><br></p>
  <h1 data-lake-id="Xeoxf" id="Xeoxf"><span data-lake-id="ucda1db73" id="ucda1db73">扩展知识</span></h1>
  <h2 data-lake-id="AI3Wy" id="AI3Wy"><span data-lake-id="ueb8b855d" id="ueb8b855d">死信队列</span></h2>
  <p data-lake-id="ub1a23df2" id="ub1a23df2"><br></p>
  <p data-lake-id="u9b2eb4be" id="u9b2eb4be"><span data-lake-id="u84bc31d0" id="u84bc31d0">当RabbitMQ中的一条正常的消息，因为过了存活时间（TTL过期）、队列长度超限、被消费者拒绝等原因无法被消费时，就会变成Dead Message，即死信。</span></p>
  <p data-lake-id="uf41ae0bc" id="uf41ae0bc"><br></p>
  <p data-lake-id="u957973ac" id="u957973ac"><span data-lake-id="u2a1cc91d" id="u2a1cc91d">当一个消息变成死信之后，他就能被重新发送到死信队列中（其实是交换机-exchange）。</span></p>
  <p data-lake-id="u9df074dd" id="u9df074dd"><span data-lake-id="ub18f57b1" id="ub18f57b1">​</span><br></p>
  <p data-lake-id="u5ec7fc22" id="u5ec7fc22"><span data-lake-id="u1d8b22f1" id="u1d8b22f1">那么基于这样的机制，就可以实现延迟消息了。那就是我们给一个消息设定TTL，但是并不消费这个消息，等他过期，过期后就会进入到死信队列，然后我们再监听死信队列的消息消费就行了。</span></p>
  <p data-lake-id="ubb4de6ac" id="ubb4de6ac"><span data-lake-id="u869bfc6e" id="u869bfc6e">​</span><br></p>
  <p data-lake-id="uc0e567cb" id="uc0e567cb"><span data-lake-id="u4a84665b" id="u4a84665b">而且，RabbitMQ中的这个TTL是可以设置任意时长的，这相比于RocketMQ只支持一些固定的时长而显得更加灵活一些。</span></p>
  <p data-lake-id="u064b7882" id="u064b7882"><span data-lake-id="uf724de79" id="uf724de79">​</span><br></p>
  <p data-lake-id="uafba9125" id="uafba9125"><span data-lake-id="ue482c941" id="ue482c941">但是，死信队列的实现方式存在一个问题，那就是可能造成队头阻塞，因为队列是先进先出的，而且每次只会判断队头的消息是否过期，那么，如果队头的消息时间很长，一直都不过期，那么就会阻塞整个队列，这时候即使排在他后面的消息过期了，那么也会被一直阻塞。</span></p>
  <p data-lake-id="uc19e043c" id="uc19e043c"><span data-lake-id="u0b12550a" id="u0b12550a">​</span><br></p>
  <p data-lake-id="u7545c7da" id="u7545c7da"><span data-lake-id="uc3bb1a85" id="uc3bb1a85">基于RabbitMQ的死信队列，可以实现延迟消息，非常灵活的实现定时关单，并且借助RabbitMQ的集群扩展性，可以实现高可用，以及处理大并发量。他的缺点第一是可能存在消息阻塞的问题，还有就是方案比较复杂，不仅要依赖RabbitMQ，而且还需要声明很多队列出来，增加系统的复杂度</span></p>
  <h2 data-lake-id="gSPpw" id="gSPpw"><br></h2>
  <h2 data-lake-id="qrqd8" id="qrqd8"><span data-lake-id="u0b48e427" id="u0b48e427">RabbitMQ插件</span></h2>
  <p data-lake-id="u1d8d5450" id="u1d8d5450"><span data-lake-id="ub2fe56fa" id="ub2fe56fa">其实，基于RabbitMQ的话，可以不用死信队列也能实现延迟消息，那就是基于rabbitmq_delayed_message_exchange插件，这种方案能够解决通过死信队列实现延迟消息出现的消息阻塞问题。但是该插件从RabbitMQ的3.6.12开始支持的，所以对版本有要求。</span></p>
  <p data-lake-id="u744f7080" id="u744f7080"><span data-lake-id="u8f2dbad3" id="u8f2dbad3">​</span><br></p>
  <p data-lake-id="u3266b27d" id="u3266b27d"><img src="https://cdn.nlark.com/yuque/0/2022/jpeg/5378072/1671869294254-32000976-4398-458d-b2fa-a5f3ce3e2119.jpeg?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_73%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"></p>
  <p data-lake-id="u8e31c20e" id="u8e31c20e"><span data-lake-id="u27b92448" id="u27b92448">这个插件是官方出的，可以放心使用，安装并启用这个插件之后，就可以创建x-delayed-message类型的交换机了。</span></p>
  <p data-lake-id="u80b4d86f" id="u80b4d86f"><span data-lake-id="u285c9dc9" id="u285c9dc9">​</span><br></p>
  <p data-lake-id="uc9f9a8cd" id="uc9f9a8cd"><span data-lake-id="ue7a004ca" id="ue7a004ca">前面我们提到的基于死信队列的方式，是消息先会投递到一个正常队列，在TTL过期后进入死信队列。但是基于插件的这种方式，消息并不会立即进入队列，而是先把他们保存在一个基于Erlang开发的Mnesia数据库中，然后通过一个定时器去查询需要被投递的消息，再把他们投递到x-delayed-message交换机中。</span></p>
  <p data-lake-id="u5314e124" id="u5314e124"><span data-lake-id="u79e42ae5" id="u79e42ae5">​</span><br></p>
  <p data-lake-id="u2dae1018" id="u2dae1018"><span data-lake-id="u03b570d8" id="u03b570d8">基于RabbitMQ插件的方式可以实现延迟消息，并且不存在消息阻塞的问题，但是因为是基于插件的，而这个插件支持的最大延长时间是(2^32)-1 毫秒，大约49天，超过这个时间就会被立即消费。</span></p>
  <p data-lake-id="u570eb11e" id="u570eb11e"><br></p>
  <p data-lake-id="uad575f3c" id="uad575f3c"><span data-lake-id="u12ab1921" id="u12ab1921">不过这个方案也有一定的限制，它将延迟消息存在于 Mnesia 表中，并且在当前节点上具有单个磁盘副本，存在丢失的可能。</span></p>
  <p data-lake-id="u8eb10899" id="u8eb10899"><span data-lake-id="u73bed3dd" id="u73bed3dd">​</span><br></p>
  <p data-lake-id="u92d70c26" id="u92d70c26"><span data-lake-id="u908ba398" id="u908ba398">目前该插件的当前设计并不真正适合包含大量延迟消息（例如数十万或数百万）的场景，详情参见 </span><a href="https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Frabbitmq%2Frabbitmq-delayed-message-exchange%2Fissues%2F72" target="_blank" data-lake-id="u52a00bf4" id="u52a00bf4"><span data-lake-id="ubaa1724d" id="ubaa1724d">#/issues/72</span></a><span data-lake-id="u2cb5ab80" id="u2cb5ab80"> 另外该插件的一个可变性来源是依赖于 Erlang 计时器，在系统中使用了一定数量的长时间计时器之后，它们开始争用调度程序资源，并且时间漂移不断累积。（</span><span data-lake-id="uabe77729" id="uabe77729">​</span></p>
  <p data-lake-id="u18cc1a5c" id="u18cc1a5c"><a href="https://github.com/rabbitmq/rabbitmq-delayed-message-exchange#limitations" target="_blank" data-lake-id="uf4f78bba" id="uf4f78bba"><span data-lake-id="ufa246604" id="ufa246604" class="lake-fontsize-12" style="color: rgb(31, 35, 40)">https://github.com/rabbitmq/rabbitmq-delayed-message-exchange#limitations</span></a><span data-lake-id="u0492a38f" id="u0492a38f"> ）</span></p>
  <p data-lake-id="uaff76406" id="uaff76406"><br></p>
 </body>
</html>